package com.bluexmicro.android.ota

import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.content.Context
import android.os.Build
import android.util.Log
import com.bluexmicro.android.coroutineble.BluetoothPeripheral
import com.bluexmicro.android.ota.entity.Constant
import com.bluexmicro.android.ota.entity.OtaCallback
import com.bluexmicro.android.ota.entity.OtaState
import com.bluexmicro.android.ota.entity.OtaTask
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
import java.util.*
import kotlin.jvm.Throws

/**
 * ## FastOta 和 Ota 是不互相兼容的
 *
 * @see OtaManager.startOta(OtaTask,OtaCallback) support kotlin and java
 * @see OtaManager.startOta(OtaTask) support kotlin
 *
 * @see OtaManager.startFastOta(List<OtaTask>,OtaCallback) support kotlin and java
 * @see OtaManager.startFastOta(List<OtaTask>) support kotlin
 *
 * 1. FastMode 一次连接可以支持至少一个文件的升级，单次发送的数据量支持23~517bytes（具体按照蓝牙协商的MTU大小决定）
 * 2. 非FastMode 一次连接只可以升级一个文件，且一次发送的数据长度为23bytes
 * 开发者选择哪一种模式进行升级请咨询供应商
 *
 */
class OtaManager {

    private val context: Context
    private val device: BluetoothDevice
    private val coroutineOta: CoroutineOta

    constructor(context: Context, device: BluetoothDevice) {
        this.context = context
        this.device = device
        coroutineOta = CoroutineOta(context, device)
    }

    /**
     * 使用两个构造函数的原因是java不能识别Kotlin的参数默认值
     */
    constructor(
        context: Context,
        device: BluetoothDevice,
        otaServiceUuid: UUID,
        otaCtrlUuid: UUID,
        otaDataUuid: UUID
    ) {
        this.context = context
        this.device = device
        coroutineOta = CoroutineOta(context, device, otaServiceUuid, otaCtrlUuid, otaDataUuid)
    }


    fun startOta(task: OtaTask): Flow<OtaState> {
        return innerExecute(listOf(task), false)
    }

    fun startFastOta(tasks: List<OtaTask>): Flow<OtaState> {
        return innerExecute(tasks, true)
    }

    private var coroutineScope: CoroutineScope? = null
    fun startOta(task: OtaTask, callback: OtaCallback) {
        if (coroutineScope?.isActive == true) {
            callback.onInitialError("Maybe there is another OTA task in progress.")
            return
        }
        coroutineScope = CoroutineScope(Dispatchers.Default)
        coroutineScope!!.launch {
            innerExecute(listOf(task), false).catch {
                callback.onInitialError(it.message ?: "Unknown Error!")
            }.collect {
                callback.onStateChanged(it)
            }
            callback.onFinish()
        }
    }

    fun startFastOta(tasks: List<OtaTask>, callback: OtaCallback) {
        if (coroutineScope?.isActive == true) {
            callback.onInitialError("Maybe there is another OTA task in progress.")
            return
        }
        coroutineScope = CoroutineScope(Dispatchers.Default)
        coroutineScope!!.launch {
            innerExecute(tasks, true).catch {
                callback.onFinish()
            }.collect {
                callback.onStateChanged(it)
            }
            callback.onFinish()
        }
    }

    fun cancel() {
        coroutineScope?.takeIf { it.isActive }?.apply { cancel() }
    }

    fun release() {
        cancel()
        coroutineOta.release()
    }

    private fun innerExecute(tasks: List<OtaTask>, fastMode: Boolean) = flow {
        coroutineOta.startTransaction(fastMode)
        val state = OtaState()
        try {
            tasks.forEachIndexed { index, otaTask ->
                state.index = index
                state.startTimeMillis = System.currentTimeMillis()
                emit(state)
                coroutineOta.execute(otaTask, fastMode).catch {
                    state.result = false
                    state.endTimeMillis = System.currentTimeMillis()
                    state.errorMessage = it.message
                    emit(state)
                }.collect { progress ->
                    state.progress = progress
                    emit(state)
                }
            }
            if (state.endTimeMillis == 0L) {
                state.result = true
                state.endTimeMillis = System.currentTimeMillis()
                emit(state)
            }
        } finally {
            coroutineOta.endTransaction(fastMode)
        }
    }

}

